/**
 * @file my_uart.c
 * @author sevenfite (lin481399413@163.com) 东莞理工机器人创新实验室
 * @brief 串口接收数据处理
 * @version 0.1
 * @date 2024-11-10
 * @verbatim  
 *   1.实现了printf函数的重定向
 *   2.实现了串口接收数据处理
 *      (1)支持中断接收，需要有结束符\r\n
 *      (2)支持DMA接收，不需要结束符
 * @endverbatim
 * @example
 * @code
 *   定义变量和函数：
 *   char uart1_rx_buf[100];//定义接收缓冲区,使用静态分配内存时需要
 *   USART_Data usart_Data_handle;//定义串口数据结构体
 *   void uart1_callback(USART_Data *me){//可选,定义串口接收完成后执行的函数
 *     //不能使用阻塞函数，包括阻塞型串口发送函数
 *     //do something
 *   }
 *   main函数中的初始化：
 *   //初始化串口接收数据结构体,使用静态分配内存,使用DMA,有回调函数,如果不使用回调,传NULL
 *   USART_DataTypeInit(&usart_Data_handle, &huart1, (uint8_t *)uart1_rx_buf, sizeof(uart1_rx_buf), DMA_MODE, uart1_callback);
 * @endcode
 * 使用方式：
 *     (1)可以使用回调函数自行处理接收到的数据，但是要注意处理的时间不能过长，否则会影响串口接收
 *     (2)可以使用USART_DataIsReceived(&usart_Data_handle)判断是否接收到数据,
 *        如果接收到数据，使用USART_DataResetReceivedFlag(&usart_Data_handle)清除接收完成标志位,
 *        以便下次接收数据
 *     (3)使用USART_DataGetReceivedLen(&usart_Data_handle)获取接收到的数据长度，
 *        使用USART_DataCleanReceivedLen(&usart_Data_handle)清除接收到的数据长度,
 *        以便下次使用
 *     (4)使用USART_GetData(&usart_Data_handle)获取接收到的数据的指针,在动态分配内存时比较有用(静态也能用)
 *     (5)使用轮询的方式，轮询USART_DataIsReceived(&usart_Data_handle)判断是否接收到数据
 *     (6)推荐使用静态分配加DMA的方式,静态分配更可控,DMA接收效率更高
 *     (7)此文件没有包含串口初始化函数,需要自行完成初始化配置(可以使用CubeMX生成的初始化函数)
 *        串口的接收等函数使用HAL库的函数,所以需要有串口句柄的传入
 * @note 
 *     没有使用FIFO等接收缓冲,只是简单的在缓冲区里读写,
 *     也没有做读写锁等保护,有可能在读的时候,DMA在写数据,导致数据损坏
 * @note 
 *  修改说明：2025/7/10
 *  增加了初始化函数的返回值，并且规定不能同时存在两个相同的串口句柄
 * @copyright Copyright (c) 2024
 * 
 */
#include "my_uart.h"
#if 1 //支持printf
/*注意，这里的printf是有输出缓冲的，输出的字符串要加上\r\n才会输出到串口
如果想要立即输出，可以使用fflush(stdout)刷新缓冲区
*/
#ifdef __GNUC__ //使用gcc编译器
int _write (int fd, char *pBuffer, int size)
{
    (void)fd;//去除warning
    for (int i = 0; i < size; i++)
    {
        while((Printf_UART->SR&0X40)==0);//等待上一次串口数据发送完成
        Printf_UART->DR = (uint8_t) pBuffer[i];       //写DR,串口1将发送数据
    }
    return size;
}
#else 
/*摘取自正点原子例程*/
#if (__ARMCC_VERSION >= 6010050)            /* 使用AC6编译器时 */
__asm(".global __use_no_semihosting\n\t");  /* 声明不使用半主机模式 */
__asm(".global __ARM_use_no_argv \n\t");    /* AC6下需要声明main函数为无参数格式，否则部分例程可能出现半主机模式 */

#else
/* 使用AC5编译器时, 要在这里定义__FILE 和 不使用半主机模式 */
#pragma import(__use_no_semihosting)

struct __FILE
{
    int handle;
    /* Whatever you require here. If the only file you are using is */
    /* standard output using printf() for debugging, no file handling */
    /* is required. */
};

#endif

/* 不使用半主机模式，至少需要重定义_ttywrch\_sys_exit\_sys_command_string函数,以同时兼容AC6和AC5模式 */
int _ttywrch(int ch)
{
    ch = ch;
    return ch;
}

/* 定义_sys_exit()以避免使用半主机模式 */
void _sys_exit(int x)
{
    x = x;
}

char *_sys_command_string(char *cmd, int len)
{
    return NULL;
}


/* FILE 在 stdio.h里面定义. */
FILE __stdout;

/* MDK下需要重定义fputc函数, printf函数最终会通过调用fputc输出字符串到串口 */
int fputc(int ch, FILE *f)
{
    while ((Printf_UART->SR & 0X40) == 0);     /* 等待上一个字符发送完成 */

    Printf_UART->DR = (uint8_t)ch;             /* 将要发送的字符 ch 写入到DR寄存器 */
    return ch;
}
#endif

#endif
//没有初始化串口的函数，请在main函数中调用MX_USARTx_UART_Init()初始化串口
USART_Data *usartDataHead_handle = NULL;  // 串口数据结构体链表头
static int USART_DataAttach(USART_Data *this);
#if USART_MALLOC == 1
#include <stdlib.h>
/**
 * @brief
 * @note  使用动态分配内存为接收缓冲区分配内存，开启中断接收/开启DMA接收
 * @param  this 指向自己的指针
 * @param huart 串口句柄
 * @param rxSize_Max 接收缓冲区大小
 * @param receiveMode IT_MODE:中断接收
 * DMA_MODE:DMA接收(中断接收要求有结束符\r\n，DMA接收不要求)
 * @param callback 中断接收完成后执行的函数，不需要可以传NULL
 * @warning 请勿在回调函数中执行过久的时间，否则会影响串口接收，包括阻塞性的串口发送函数
 * @note 波特率高的情况下，建议使用DMA接收
 * @return 0:成功,1:串口句柄已存在或接收结构体已初始化过,2:内存分配失败
 */

uint8_t USART_DataTypeInit(USART_Data *this, UART_HandleTypeDef *huart,
                        uint16_t rxSize_Max, uint8_t receiveMode,uartCallBack callback) {
  USART_Data* target = usartDataHead_handle;
  while(target) {
    if(target->huart == huart) return 1;	//表示已经注册
    target = target->next;
  }
  //分配内存
  this->usart_rx_buf = (uint8_t *)malloc(rxSize_Max);
  if (this->usart_rx_buf == NULL) return 2; //内存分配失败
  int ret = USART_DataAttach(this);
  if (ret != 0) {
    free(this->usart_rx_buf); //如果已经注册过了，释放内存
    return 1; //表示已经注册
  }

  this->huart = huart;
  this->rxSize_Max = rxSize_Max;
  this->callback = callback;

  if (receiveMode==IT_MODE) {
  HAL_UART_Receive_IT(this->huart, &this->rxBuffer, 1);
  } else if(receiveMode==DMA_MODE){
  HAL_UARTEx_ReceiveToIdle_DMA(this->huart, this->usart_rx_buf, this->rxSize_Max);
  __HAL_DMA_DISABLE_IT(
    this->huart->hdmarx,
    DMA_IT_HT);  // 关闭半传输中断,防止半传输中断影响接收完成标志位和接收长度
  }
  return 0; //成功
}
#else

/**
 * @brief 串口结构体初始化
 * @note  使用静态分配内存为接收缓冲区分配内存，要自己创建一个全局变量，开启中断接收/开启DMA接收
 * @param  this 指向自己的指针，需要自行创建
 * @param huart 串口句柄
 * @param rxBuffer 接收缓冲区，要求大小为rxSize_Max，自己定义的全局变量
 * @param rxSize_Max 接收缓冲区大小
 * @param receiveMode IT_MODE:中断接收 DMA_MODE：DMA接收(中断接收要求有结束符\r\n，DMA接收不要求)
 * @param callback 中断接收完成后执行的函数，不需要可以传NULL
 * @warning 请勿在回调函数中执行过久的时间，否则会影响串口接收，包括阻塞性的串口发送函数
 * @note 波特率高的情况下，建议使用DMA接收
 * @return 0:成功,1:串口句柄已存在或接收结构体已初始化过
 */
uint8_t USART_DataTypeInit(USART_Data *this, UART_HandleTypeDef *huart,uint8_t* rxBuffer,
						uint16_t rxSize_Max,UsartMode receiveMode,uartCallBack callback)
{
  USART_Data* target = usartDataHead_handle;
  while(target) {
    if(target->huart == huart) return 1;	//表示已经注册
    target = target->next;
  }
  int ret = USART_DataAttach(this);
  if (ret != 0) return 1; //表示已经注册

  this->huart = huart;
  this->rxSize_Max = rxSize_Max;
  this->usart_rx_buf = rxBuffer;
  this->callback = callback;
  this->receiveMode = receiveMode;

  if (receiveMode==IT_MODE) {
  HAL_UART_Receive_IT(this->huart, &this->rxBuffer, 1);
  } else if(receiveMode==DMA_MODE){
  HAL_UARTEx_ReceiveToIdle_DMA(this->huart, this->usart_rx_buf, this->rxSize_Max);
  __HAL_DMA_DISABLE_IT(this->huart->hdmarx , DMA_IT_HT);  // 关闭半传输中断,防止半传输中断影响接收完成标志位和接收长度
  }else if(receiveMode == DMA_CIRCULAR_MODE){
    HAL_UARTEx_ReceiveToIdle_DMA(this->huart, this->usart_rx_buf, this->rxSize_Max);
    __HAL_DMA_DISABLE_IT(this->huart->hdmarx , DMA_IT_HT);  // 关闭半传输中断,防止半传输中断影响接收完成标志位和接收长度
  }
  return 0; //成功
}
#endif
/**
 * @brief 将该结构体添加到链表中
 * @param  this 指向自己的指针
 * @return -1表示已经注册，0表示成功注册
 */
static int USART_DataAttach(USART_Data *this)
{
	USART_Data* target = usartDataHead_handle;
	while(target) {
		if(target == this) return -1;	//表示已经注册
		target = target->next;
	}
	this->next = usartDataHead_handle;
	usartDataHead_handle = this;
	return 0;
}

/**
 * @brief       串口数据接收中断回调函数
                数据处理在这里进行（中断接收模式使用）
 * @param       huart:串口句柄
 * @retval      无
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
  USART_Data* this = usartDataHead_handle;
  while(this) {
    if (huart->Instance == this->huart->Instance) {
      if ((this->usart_rx_sta & 0x8000) == 0) /* 接收未完成 */
      {
        if (this->usart_rx_sta & 0x4000) /* 接收到了0x0d（即回车键） */
        {
          if (this->rxBuffer !=
              0x0a) /* 接收到的不是0x0a（即不是换行键） */
          {
            this->usart_rx_sta = 0; /* 接收错误,重新开始 */
          } else /* 接收到的是0x0a（即换行键） */
          {
            this->usart_rx_sta |= 0x8000; /* 接收完成了 */
            if(this->callback != NULL){//执行回调函数
              this->callback((void*)this);
            }
          }
        } else /* 还没收到0X0d（即回车键） */
        {
          if (this->rxBuffer == 0x0d)
            this->usart_rx_sta |= 0x4000;
          else {
            this->usart_rx_buf[this->usart_rx_sta & 0X3FFF] =
                this->rxBuffer;
            this->usart_rx_sta++;

            if (this->usart_rx_sta > (this->rxSize_Max - 1)) {
              this->usart_rx_sta = 0; /* 接收数据错误,重新开始接收 */
            }
          }
        }
      }
      HAL_UART_Receive_IT(this->huart, &this->rxBuffer, 1);
    }
    this = this->next;
  }
}
/// @brief 串口空闲中断回调函数（DMA模式使用）
/// @param huart 串口句柄
/// @param Size 接收到的数据长度
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) {
  USART_Data* this = usartDataHead_handle;
  while(this) {
    if (huart->Instance == this->huart->Instance) {
      if(this->receiveMode == DMA_CIRCULAR_MODE) return;
      this->usart_rx_sta = Size;
      this->usart_rx_sta |= 0x8000;  // 接收完成
      HAL_UARTEx_ReceiveToIdle_DMA(this->huart,this->usart_rx_buf,this->rxSize_Max);
      __HAL_DMA_DISABLE_IT(this->huart->hdmarx,DMA_IT_HT);  // 关闭半传输中断,防止半传输中断影响接收完成标志位和接收长度
      if(this->callback != NULL)
        this->callback(this);
    }
    this = this->next;
  }
}

/**
 * @brief 环形缓冲区空闲中断回调函数
 * 
 * @param huart 
 * @param Size 
 */
void uart_Idle_Callback(UART_HandleTypeDef *huart, uint16_t Size){
  USART_Data* this = usartDataHead_handle;
  while(this){
    if(huart->Instance == this->huart->Instance){
      if(this->receiveMode != DMA_CIRCULAR_MODE) return;
      this->usart_rx_sta = Size;
      this->usart_rx_sta |= 0x8000;  // 接收完成
      if(this->callback != NULL){
        this->callback(this);
      }
    }
    this = this->next;
  }
}
/**
 * @brief 环形缓冲区DMA接收完成回调函数
 * 
 * @param huart 
 * @param Size 
 */
void uart_DMA_Rx_Full_Callback(UART_HandleTypeDef *huart, uint16_t Size){
  USART_Data* this = usartDataHead_handle;
  while (this) {
    if (huart->Instance == this->huart->Instance) {
      if (this->receiveMode != DMA_CIRCULAR_MODE) return;
      // 如果空闲标志位已经置位
      if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE)) {
        uart_Idle_Callback(huart, Size);
      } else {
        this->usart_rx_sta = Size;// 接收未完成
        USART_DataResetReceivedFlag(this);//清除接收完成标志位
        if (this->callback != NULL) {
          this->callback(this);
        }
      }
    }
    this = this->next;
  }
}
//现在和库函数是一样的了
HAL_StatusTypeDef my_UART_Start_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  uint32_t *tmp;

  huart->pRxBuffPtr = pData;
  huart->RxXferSize = Size;

  huart->ErrorCode = HAL_UART_ERROR_NONE;
  huart->RxState = HAL_UART_STATE_BUSY_RX;

  /* Set the DMA abort callback */
  huart->hdmarx->XferAbortCallback = NULL;

  /* Enable the DMA stream */
  tmp = (uint32_t *)&pData;
  HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t *)tmp, Size);

  /* Clear the Overrun flag just before enabling the DMA Rx request: can be mandatory for the second transfer */
  __HAL_UART_CLEAR_OREFLAG(huart);

  if (huart->Init.Parity != UART_PARITY_NONE)
  {
    /* Enable the UART Parity Error Interrupt */
    ATOMIC_SET_BIT(huart->Instance->CR1, USART_CR1_PEIE);
  }

  /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
  ATOMIC_SET_BIT(huart->Instance->CR3, USART_CR3_EIE);

  /* Enable the DMA transfer for the receiver request by setting the DMAR bit
  in the UART CR3 register */
  ATOMIC_SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);

  return HAL_OK;
}
/**
 * @brief 开启串口DMA空闲中断
 * @note 与HAL库的库函数略有不同，不会清除串口空闲标志位，以便在边界条件下能够判别一帧数据的结束
 * 
 * @param huart 
 * @param pData 
 * @param Size 
 * @return HAL_StatusTypeDef 
 */
HAL_StatusTypeDef my_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size){
  HAL_StatusTypeDef status;

  /* Check that a Rx process is not already ongoing */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return HAL_ERROR;
    }

    /* Set Reception type to reception till IDLE Event*/
    huart->ReceptionType = HAL_UART_RECEPTION_TOIDLE;
    huart->RxEventType = HAL_UART_RXEVENT_TC;

    ATOMIC_SET_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
    status =  my_UART_Start_Receive_DMA(huart, pData, Size);

    /* Check Rx process has been successfully started */
    if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
    {
      //不清除空闲标志位
      //__HAL_UART_CLEAR_IDLEFLAG(huart);
      //打开空闲中断的使能开关
      ATOMIC_SET_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
    }
    else
    {
      /* In case of errors already pending when reception is started,
         Interrupts may have already been raised and lead to reception abortion.
         (Overrun error for instance).
         In such case Reception Type has been reset to HAL_UART_RECEPTION_STANDARD. */
      status = HAL_ERROR;
    }

    return status;
  }
  else
  {
    return HAL_BUSY;
  }
}